package org.benetech.collections;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Map of keys to a list of values.
 * @author Reuben Firmin
 * @see Map
 * @param <T> The type of keys
 * @param <U> The type of values
 */
public final class ListMap<T, U> implements Map<T, List<U>> {

    /**
     * The delegate.
     */
    private final Map<T, List<U>> delegate = new HashMap<T, List<U>>();

	/**
	 * Default constructor.
	 */
	public ListMap() {
		// empty
	}

    /**
     * {@inheritDoc}
     */
    public List<U> put(final T key, final List<U> value) {
        return putAll(key, value);
    }

    /**
     * {@inheritDoc}
     */
    public List<U> put(final T key, final U value) {
        List<U> list = delegate.get(key);

        if (list == null) {
            list = new ArrayList<U>();
            delegate.put((T) key, list);
        }
        list.add((U) value);
        // XXX this does not meet Map's requirements
        return list;
    }

    /**
     * Add a group of mappings to this key.
     * @param key The key to add the mapping to.
     * @param values The group of values to add to the key.
     * @return the value of the new mapping.
     */
    public List<U> putAll(final T key, final Collection<U> values) {
        if (values != null) {
            for (U value : values) {
                put(key, value);
            }
        }
        return get(key);
    }

    /**
     * {@inheritDoc}
     */
    public List<U> get(final Object key) {
        return delegate.get(key);
    }

    /**
     * Not efficient! Retrieves all Us mapped to a key of the given characters, ignoring case.
     * @param key The key
     * @return never null
     */
    public List<U> getIgnoreCase(final String key) {
    	final List<U> out = new ArrayList<U>();
    	for (Map.Entry<T, List<U>> entry : delegate.entrySet()) {
    		if (entry.getKey() instanceof String) {
    			final String keyCandidate = (String) entry.getKey();
    			if (key.equalsIgnoreCase(keyCandidate)) {
    				out.addAll(entry.getValue());
    			}
    		}
    	}
    	return out;
    }

    /**
     * Get the first object mapped against this key. Null if none.
     * @param key The key that's mapped
     * @return May be null
     */
    public U getFirst(final T key) {
    	final List<U> out = get(key);
    	if (out != null && out.size() > 0) {
    		return out.get(0);
    	}
    	return null;
    }

    /**
     * {@inheritDoc}
     */
    public void clear() {
        delegate.clear();
    }

    /**
     * {@inheritDoc}
     */
    public boolean containsKey(final Object key) {
        return delegate.containsKey(key);
    }

    /**
     * {@inheritDoc}
     */
    public boolean containsValue(final Object value) {
        return delegate.containsValue(value);
    }

    /**
     * {@inheritDoc}
     */
    public Set<Map.Entry<T, List<U>>> entrySet() {
        return delegate.entrySet();
    }

    /**
     * {@inheritDoc}
     */
    public boolean isEmpty() {
        return delegate.isEmpty();
    }

    /**
     * {@inheritDoc}
     */
    public Set<T> keySet() {
        return delegate.keySet();
    }

    /**
     * {@inheritDoc}
     */
    public void putAll(final Map t) {
        if (t instanceof ListMap) {
        	for (Object entry : t.entrySet()) {
        		putAll((T)((Map.Entry) entry).getKey(), (List)((Map.Entry) entry).getValue());
        	}
        } else {
        	delegate.putAll(t);
        }

    }

    /**
     * {@inheritDoc}
     */
    public List<U> remove(final Object key) {
        return delegate.remove(key);
    }

    /**
     * Size of all lists aggregated.
     * @return size.
     */
    public int size() {
        int count = 0;
        for (List<U> value : values()) {
            count += value.size();
        }
        return count;
    }

    /**
     * {@inheritDoc}
     */
    public Collection<List<U>> values() {
        return delegate.values();
    }

    /**
     * The total size of all lists.
     * @return not null
     */
    public int getTotalSize() {
        return size();
    }
}
